import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class ServerComunication implements ServerComm {

	private static final int SERVER_PORT = 8080;
	private ServerSocket serverSocket;
	private DealWithClient dealWithClient;
	private BlockingQueue<MessageToServer> toServerQueue = new LinkedBlockingQueue<MessageToServer>();
	private HashMap<String, DealWithClient> DealWithClientList = new HashMap<String, DealWithClient>();

	/**
	 * Starts the Birdsong server. The method should setup a server socket and
	 * begin to accept connections. This method must return immediately after a
	 * server socket has been setup, and another thread should started to listen
	 * for connections.
	 */
	@Override
	public void start() {
		new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					serverSocket = new ServerSocket(SERVER_PORT);
					while (true) {
						Socket socket = serverSocket.accept();
						DealWithClient newClient = new DealWithClient(socket,
								ServerComunication.this);
					}
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

			}
		}).start();

	}

	/**
	 * Get the next message received from one of the clients. If no message is
	 * has been received, the method blocks until a message has been received.
	 * 
	 * @return The next message sent by one of the connected clients to the
	 *         server
	 */
	@Override
	public ServerSideMessage getNextMessage() {
		try {
			return toServerQueue.take();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Checks if a message from a client is pending. If
	 * {@link #hasNextMessage()} returns <b>true</b>, a call to
	 * {@link #getNextMessage()} will return immediately with the oldest message
	 * in the queue. If {@link #hasNextMessage()} returns <b>false</b>, a call
	 * to {@link #getNextMessage()} will block until a message has been
	 * received.
	 * 
	 * @return <b>true</b> if a message from a client is currently pending,
	 *         otherwise <b>false</b>.
	 */
	@Override
	public boolean hasNextMessage() {
		if (!toServerQueue.isEmpty())
			return true;
		else
			return false;
	}

	/**
	 * Send a message to a client. This method should be used to relay messages
	 * received from a client to a follower. This method should be called once
	 * for each follower.
	 * 
	 * @param fromNicename
	 *            The nickname of the client from which the message was sent
	 * @param toNickname
	 *            The nickname of a follower
	 * @param birdsongMessage
	 *            The message
	 */
	@Override
	public void sendBirdsongMessage(String fromNicename, String toNickname,
			String birdsongMessage) {
		MessageToClient message = new MessageToClient(
				ClientSideMessage.Type.BIRDSONG_MESSAGE, birdsongMessage,
				fromNicename);
		DealWithClientList.get(toNickname).addToClientQueue(message);
	}

	/**
	 * Inform a client that another client is now a follower.
	 * 
	 * @param toNickname
	 *            nickname of the client who has a new follower
	 * @param newFollower
	 *            nickname of the new follower
	 */
	@Override
	public void sendNewFollower(String toNickname, String newFollower) {
		MessageToClient message = new MessageToClient(
				ClientSideMessage.Type.FOLLOWER, newFollower,
				ClientSideMessage.SERVER);
		DealWithClientList.get(toNickname).addToClientQueue(message);
	}

	/**
	 * Inform a client that a former follower has decided to unfollow.
	 * 
	 * @param toNickname
	 *            nickname of the client who has lost a follower
	 * @param unfollower
	 *            nickname of the unfollower
	 */
	@Override
	public void sendNewUnfollow(String toNickname, String unfollower) {
		MessageToClient message = new MessageToClient(
				ClientSideMessage.Type.UNFOLLOWER, unfollower,
				ClientSideMessage.SERVER);
		DealWithClientList.get(toNickname).addToClientQueue(message);
	}

	/**
	 * Report an error to a client. An error can, for instance, be trying to
	 * follow a client who is not connected, trying to unfollow a client not
	 * currently followed, and so on.
	 * 
	 * @param toNickname
	 *            nickname of the client.
	 * @param error
	 *            description of the error.
	 */
	@Override
	public void sendError(String toNickname, String error) {
		MessageToClient message = new MessageToClient(
				ClientSideMessage.Type.ERROR, error, ClientSideMessage.SERVER);
		DealWithClientList.get(toNickname).addToClientQueue(message);
	}

	/**
	 * Inform a client that a new client has connected. When a new client
	 * connects, the server should call this method for each client already
	 * connected to inform the newly connected client about the clients
	 * currently online.
	 * 
	 * @param toNickname
	 *            nickname of the client to whom to send the message.
	 * @param connectedNickname
	 *            nickname of the connected client.
	 */
	@Override
	public void sendClientConnected(String toNickname, String connectedNickname) {
		MessageToClient message = new MessageToClient(
				ClientSideMessage.Type.CONNECTED, connectedNickname,
				ClientSideMessage.SERVER);
		DealWithClientList.get(toNickname).addToClientQueue(message);

	}

	/**
	 * Inform a client that another client disconnected.
	 * 
	 * @param toNickname
	 *            nickname of the client to whom to send the message.
	 * @param disconnectedNickname
	 *            nickname of client who disconnected.
	 */
	@Override
	public void sendClientDisconnected(String toNickname,
			String disconnectedNickname) {
		MessageToClient message = new MessageToClient(
				ClientSideMessage.Type.DISCONNECTED, disconnectedNickname,
				ClientSideMessage.SERVER);
		DealWithClientList.get(toNickname).addToClientQueue(message);
	}

	/**
	 * Checks if a client with a certain nickname is currently connected.
	 * 
	 * @param nickname
	 *            nickname of the client to check.
	 * @return <b>true</b> if a client with the nickname is currently connected,
	 *         otherwise <b>false</b>.
	 */
	@Override
	public boolean clientIsConnected(String nickname) {
		if (!DealWithClientList.containsKey(nickname))
			return false;
		else
			return true;
	}

	/**
	 * Disconnect a client.
	 * 
	 * @param nickname
	 *            nickname of the client to disconnect.
	 */
	@Override
	public void disconnectClient(String nickname) {
		DealWithClient dc = DealWithClientList.get(nickname);
		dc.closeClient();
		DealWithClientList.remove(nickname);
	}

	/**
	 * Gets the blocking queue.
	 * 
	 * @return blocking queue.
	 */
	public BlockingQueue<MessageToServer> getToServerQueue() {
		return toServerQueue;
	}

	/**
	 * Gets the HashMap.
	 * 
	 * @return HashMap.
	 */
	public HashMap<String, DealWithClient> getDealWithClientList() {
		return DealWithClientList;
	}

}
